package com.by.datasource;

import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.by.datasource.datapermission.DataPermission;
import com.by.datasource.dynamictablename.DyTN;
import com.by.datasource.dynamictablename.algorithm.annotation.DyTNAlgorithm;
import com.by.datasource.interceptor.SqlTimeInterceptor;
import com.by.datasource.properties.ByDatasourceProperties;
import com.by.datasource.tenant.TCorpCode;
import com.by.datasource.tenant.TProjectId;
import com.by.toolkitspring.customizeannotation.ByScan;
import com.google.common.collect.Lists;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Optional;

@Slf4j
@EnableScheduling
@EnableTransactionManagement
@ComponentScan(value = {"com.by.datasource"})
@ConditionalOnProperty(name = "by.datasource.enabled", havingValue = "true")
@EnableConfigurationProperties(ByDatasourceProperties.class)
@ByScan(basePackage = "com.by", annotationClass = {TCorpCode.class, TProjectId.class, DataPermission.class, DyTN.class, DyTNAlgorithm.class})
public class ByDatasourceAutoConfiguration {

    @Resource
    private ByDatasourceProperties byDatasourceProperties;

    @Resource
    private MybatisPlusInterceptor mybatisPlusInterceptor;

    @Resource
    private MetaObjectHandler metaObjectHandler;

    @Resource
    private Environment environment;

    @Resource
    private SqlTimeInterceptor sqlTimeInterceptor;

    public static final String DB_NAME_VARIABLE = "__schema__";
    public static final String DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver";

    public static final Integer INITIAL_SIZE = 10;
    public static final Integer MIN_IDLE = 10;
    public static final Integer MAX_ACTIVE = 30;

    public static final Integer MAX_WAIT = 30000;
    public static final Integer QUERY_TIMEOUT = 40;

    @Bean
    @Primary
    public DataSource initDataSource() throws SQLException {
        String dbName = byDatasourceProperties.getDbName();
        DruidDataSource druidDataSource = createDruidDataSource(dbName, dbName);
        druidDataSource.init();
        return druidDataSource;
    }

    /**
     * Init DruidDataSource
     *
     * @param dbName
     * @param dataSourceName
     * @return
     */
    private DruidDataSource createDruidDataSource(String dbName, String dataSourceName) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setName(dataSourceName);
        dataSource.setDriverClassName(DRIVER_CLASS_NAME);
        dataSource.setUrl(byDatasourceProperties.getUrl().replace(DB_NAME_VARIABLE, dbName));
        dataSource.setUsername(byDatasourceProperties.getUserName());
        dataSource.setPassword(byDatasourceProperties.getPassword());

        dataSource.setInitialSize(Optional.ofNullable(byDatasourceProperties.getInitialSize()).orElse(INITIAL_SIZE));
        dataSource.setMinIdle(Optional.ofNullable(byDatasourceProperties.getMinIdle()).orElse(MIN_IDLE));
        dataSource.setMaxActive(Optional.ofNullable(byDatasourceProperties.getMaxActive()).orElse(MAX_ACTIVE));
        dataSource.setMaxWait(Optional.ofNullable(byDatasourceProperties.getMaxWait()).orElse(MAX_WAIT));
        dataSource.setQueryTimeout(Optional.ofNullable(byDatasourceProperties.getQueryTimeout()).orElse(QUERY_TIMEOUT));

        // 每25分钟做一次空闲连接回收操作
        dataSource.setTimeBetweenEvictionRunsMillis(10L * 60L * 1000L);
        dataSource.setValidationQuery("SELECT 'x'");
        dataSource.setTestWhileIdle(true);
        dataSource.setTestOnBorrow(false);
        dataSource.setTestOnReturn(false);
        dataSource.setPoolPreparedStatements(false);
        dataSource.setMinEvictableIdleTimeMillis(30L * 60L * 1000L);
        dataSource.setMaxEvictableIdleTimeMillis(60L * 60L * 1000L);
        dataSource.setPoolPreparedStatements(false);
        log.info("[ByDatasourceAutoConfiguration][createDruidDataSource] dbName:[{}] keepAliveIsTrue", dbName);
        dataSource.setKeepAlive(true);
        // 每6分钟做一次保活
        dataSource.setKeepAliveBetweenTimeMillis(25L * 60L * 1000L);

        dataSource.setUseUnfairLock(true);
        //dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
        dataSource.setProxyFilters(Lists.newArrayList(druidLogFilter()));
        return dataSource;
    }

    /**
     * druid执行日志（只打印执行语句+执行时间）
     *
     * @return
     */
    @Bean
    public Slf4jLogFilter druidLogFilter() {
        Slf4jLogFilter logFilter = new Slf4jLogFilter();
        logFilter.setStatementLoggerName("druidSql");
        logFilter.setStatementExecutableSqlLogEnable(false);
        logFilter.setStatementCreateAfterLogEnabled(false);
        logFilter.setStatementPrepareAfterLogEnabled(false);
        logFilter.setStatementPrepareCallAfterLogEnabled(false);
        logFilter.setStatementCloseAfterLogEnabled(false);
        logFilter.setStatementParameterSetLogEnabled(false);
        logFilter.setStatementParameterClearLogEnable(false);
        logFilter.setStatementLogErrorEnabled(false);
        return logFilter;
    }

    @Bean
    @Primary
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*Mapper.xml"));

        // 读取配置文件中的配置
        MybatisConfiguration configuration = Binder.get(environment).bindOrCreate("mybatis-plus.configuration", MybatisConfiguration.class);
        GlobalConfig globalConfig = Binder.get(environment).bindOrCreate("mybatis-plus.global-config", GlobalConfig.class);
        globalConfig.setMetaObjectHandler(metaObjectHandler);

        // mybatis-plus配置
        sqlSessionFactory.setConfiguration(configuration);
        // mybatis-plus全局配置
        sqlSessionFactory.setGlobalConfig(globalConfig);
        // 插件
        sqlSessionFactory.setPlugins(mybatisPlusInterceptor, sqlTimeInterceptor);
        return sqlSessionFactory.getObject();
    }

    @Bean
    @Primary
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    
}
